﻿using RpcCacheClient.Interface;
using RpcHelper;
using RpcModel;
using RpcModularModel.Accredit;
using RpcModularModel.Accredit.Model;
using RpcSyncService.Interface;

namespace RpcSyncService.Accredit
{
    [RpcClient.Attr.UnityName("redis")]
    [RpcClient.Attr.ClassLifetimeAttr(RpcClient.Attr.ClassLifetimeType.单例)]
    internal class RedisAccreditToken : IAccreditToken
    {
        private AccreditToken _Token;

        private string _CacheKey;
        private const string _CacheName = "Accredit_";

        private IRedisCache _Cache;
        private readonly IAccreditServer _Server;
        public RedisAccreditToken(IRedisCache redis,
            IAccreditServer accredit)
        {
            _Server = accredit;
            _Cache = redis;
        }
       
        public Guid AccreditId
        {
            get;
            private set;
        }
        public string CheckKey => this._Token.CheckKey;

        public AccreditToken Token => this._Token;

        private void _InitCache()
        {
            this._CacheKey = _CacheName + this.AccreditId;
        }
        public void Create(AccreditToken token)
        {
            this.AccreditId = token.AccreditId;
            this._Token = token;
            this._InitCache();
            TimeSpan? time = token.GetOverTime();
            if (!time.HasValue)
            {
                throw new ErrorException("accredit.expire");
            }
            else if (!this._Cache.Add(this._CacheKey, token, time.Value))
            {
                throw new ErrorException("accredit.add.error");
            }
            else if (token.PAccreditId.HasValue)
            {
                string subKey = "AccreditSub_" + token.PAccreditId.Value;
                try
                {
                    this._Cache.ListAppend(subKey, this.AccreditId);
                }
                catch (Exception e)
                {
                    this._Cache.Remove(this._CacheKey);
                    throw ErrorException.FormatError(e);
                }
            }
        }
        public AccreditDatum Get()
        {
            return new AccreditDatum
            {
                RoleType = this._Token.RoleType,
                StateVer = this._Token.StateVer,
                State = this._Token.State,
                Accredit = new AccreditRes
                {
                    AccreditId = this._Token.AccreditId,
                    CheckKey = this._Token.CheckKey,
                    SysGroup = this._Token.SysGroup,
                    ApplyId = this._Token.ApplyId,
                    RpcMerId = this._Token.RpcMerId,
                    Expire = this._Token.Expire
                }
            };
        }
        public bool Set(SetAccredit obj)
        {
            this._Token.AccreditRole = obj.AccreditRole ?? Array.Empty<string>();
            this._Token.State = obj.State;
            this._Token.Expire = !obj.Expire.HasValue ? null : DateTime.Now.AddSeconds(obj.Expire.Value);
            this._Token.StateVer += 1;
            return this._SaveToken();
        }
        public TimeSpan? Refresh()
        {
            if (this._Token.Expire.HasValue)
            {
                return null;
            }
            TimeSpan time = AccreditHelper.GetDefAccreditTime();
            if (this._Cache.SetExpire(this._CacheKey, DateTime.Now.Add(time)))
            {
                return time;
            }
            return null;
        }


        private bool _SaveToken()
        {
            TimeSpan? time = this._Token.GetOverTime();
            if (!time.HasValue)
            {
                throw new ErrorException("accredit.Invalid");
            }
            AccreditToken newToken = this._Cache.AddOrUpdate<AccreditToken>(this._CacheKey, this._Token, (a, b) =>
            {
                if (a.StateVer < b.StateVer)
                {
                    return b;
                }
                return null;
            }, time.Value);
            if (newToken == null)
            {
                throw new ErrorException("accredit.set.fail");
            }
            else if (!newToken.Equals(this._Token))
            {
                this._Token = newToken;
                this._Token.Refresh(true);
                return true;
            }
            return false;
        }
        public bool SetState(string state)
        {
            this._Token.State = state;
            this._Token.StateVer += 1;
            return this._SaveToken();
        }
        public bool Cancel()
        {
            if (this.Remove(out Guid[] keys))
            {
                this._Token.Refresh(true);
                if (!keys.IsNull())
                {
                    this._Server.Cancel(keys);
                }
                return true;
            }
            return false;
        }
        public bool Remove(out Guid[] subs)
        {
            if (!this._Cache.Remove(this._CacheKey))
            {
                subs = null;
                return false;
            }
            string key = "AccreditSub_" + this._Token.PAccreditId.Value;
            if (this._Cache.GetList(key, out subs))
            {
                this._Cache.Remove(key);
            }
            return true;
        }
        public bool Init(Guid accreditId)
        {
            this.AccreditId = accreditId;
            this._InitCache();
            if (this._Cache.TryGet(this._CacheKey, out AccreditToken token))
            {
                this._Token = token;
                if (token.Expire.HasValue)
                {
                    return token.Expire.Value > DateTime.Now;
                }
                return true;
            }
            return false;
        }



        public bool CheckRole(MsgSource source)
        {
            return this._Token.AccreditRole.IsExists(a => a == source.SystemType || a == source.SysGroup);
        }
    }
}
